package cn.com.chengmandian.core.tenant.filters;

import cn.com.chengmandian.core.common.constants.CommonConstant;
import cn.com.chengmandian.core.common.constants.ResponseCode;
import cn.com.chengmandian.core.common.enums.ClientsEnum;
import cn.com.chengmandian.core.common.utils.UrlUtil;
import cn.com.chengmandian.core.tenant.cache.TenantCacheService;
import cn.com.chengmandian.core.tenant.pojo.LoginUser;
import cn.com.chengmandian.core.tenant.pojo.TenantInfo;
import cn.com.chengmandian.core.tenant.properties.WebTenantProperties;
import cn.com.chengmandian.core.tenant.utils.AuthUtil;
import cn.com.chengmandian.core.tenant.TenantUtil;
import cn.com.chengmandian.core.web.pojo.AjaxResult;
import cn.com.chengmandian.core.web.utils.SpringContextUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@Slf4j
@Component
public class RequestAuthFilter extends OncePerRequestFilter {
    @Autowired
    private WebTenantProperties properties;

    @Autowired
    private TenantCacheService cacheService;

    // 403 权限不足
    private void responseError(HttpServletResponse response, Integer code, String message) {
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(JSONUtil.toJsonStr(AjaxResult.error(code, message)));
        } catch (IOException e) {
            log.error(e.getMessage());
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }

    // 默认白名单
    private static final List<String> defaultWhiteList = ListUtil.toList(
            "/*/swagger-resources",
            "/*/swagger-resources/**",
            "/*/swagger-ui/**",
            "/*/v3/api-docs",
            "/*/open/**", // 外部接口
            "/*/inner/**");// 内部接口

    private void printRequest(HttpServletRequest request) {
        if (!SpringContextUtil.isDev()) return;
        log.info("=============================================================");
        log.info("RequestURI:{}", request.getRequestURI());
        if (request.getParameterMap() != null && request.getParameterMap().size() > 0) {
            log.info("ParameterMap:{}", JSONUtil.toJsonStr(request.getParameterMap()));
        }
        log.info("=============================================================");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        printRequest(request);
        String requestURI = request.getRequestURI();
        for (String path : defaultWhiteList) {
            if (UrlUtil.filterUrls(path, requestURI)) {
                filterChain.doFilter(request, response);
                return;
            }
        }

        // 识别客户端
        String clientId = request.getHeader(CommonConstant.HEADER_CLIENT_ID);
        if (StrUtil.isBlank(clientId)) clientId = request.getServerName();
        ClientsEnum client = ClientsEnum.codeOf(clientId);
        if (client == null) {
            log.error("客户端错误: {}", clientId);
            responseError(response, ResponseCode.ACCESS_DENIED, "客户端错误");
            return;
        }

        // 如果应用配置了用户类型
        if (StrUtil.isNotEmpty(properties.getUserType())) {
            // 如果配置的用户类型和提交的客户端用户类型不匹配
            if (!ArrayUtil.contains(properties.getUserType().split(","), client.getUserType())) {
                log.error("非法用户类型: {}", properties.getUserType());
                responseError(response, ResponseCode.ACCESS_DENIED, "非法用户类型");
                return;
            }
        }

        // 无需识别代理信息的接口，默认也无需登陆
        if (CollectionUtil.isNotEmpty(properties.getNoTenantList())) {
            for (String path : properties.getNoTenantList()) {
                if (UrlUtil.filterUrls(path, requestURI)) {
                    filterChain.doFilter(request, response);
                    return;
                }
            }
        }

        String tenantNo = null;
        if (properties.getByHeader()) {
            tenantNo = request.getHeader(CommonConstant.HEADER_TENANT);
        }
        // 支持从域名获取租户信息
        if (StrUtil.isBlank(tenantNo) && properties.getByDomain()) {
            tenantNo = request.getServerName();
        }
        TenantInfo tenantInfo = StrUtil.isNotBlank(tenantNo) ? cacheService.get(tenantNo) : null;
        if (tenantInfo == null) {
            // 支持从用户信息获取租户信息
            if (properties.getByUser()) {
                LoginUser loginUser = AuthUtil.getUser(request, client);
                if (loginUser == null) {
                    log.error("租户标识不存在: {}", tenantNo);
                    responseError(response, ResponseCode.ACCESS_FORBIDDEN, "租户标识不存在");
                    return;
                }
                tenantInfo = cacheService.get(loginUser.getTenantId() + "");
            } else {
                log.error("租户标识不存在: {}", tenantNo);
                responseError(response, ResponseCode.ACCESS_FORBIDDEN, "租户标识不存在");
                return;
            }
        }
        TenantUtil.set(tenantInfo);

        // 无需登陆的接口
        if (CollectionUtil.isNotEmpty(properties.getWhiteList())) {
            for (String path : properties.getWhiteList()) {
                if (UrlUtil.filterUrls(path, requestURI)) {
                    filterChain.doFilter(request, response);
                    return;
                }
            }
        }

        // 获取，并校验
        LoginUser loginUser = AuthUtil.getUser(request, client, tenantInfo.getTokenSecret());
        if (loginUser == null) {
            TenantUtil.remove();
            responseError(response, ResponseCode.TOKEN_INVALID, "登录信息失效：" + request.getRequestURI());
            return;
        }

        // 禁止重复登陆
        if (!client.getRepeatLogin()) {
            if (!AuthUtil.checkTenant(client, loginUser, request, tenantInfo.getTokenSecret())) {
                responseError(response, ResponseCode.TOKEN_INVALID, "登陆信息已失效：" + request.getRequestURI());
                return;
            }
        }
        filterChain.doFilter(request, response);

        TenantUtil.remove();
    }

}
